要介紹 forwardRef 之前,我們要先知道 HOC 這個 React 特有的設計概念。
Higher-Order Component
是一種設計概念:「把一個 Component 傳進去給 HOC Function,它會幫你加工成為一個新的有額外功能或資料的增強版 Component」。
const EnhancedComponent = aHOCFunction(WrappedComponent);
forwardRef 會傳入一個 Function Component,此 FC 除了傳入 props,也會傳入 ref 物件參考,再將它們傳到 JSX 中。當 ref 物件傳進內部後,就可以標記任意的 DOM 元素。
forwardRef:傳送 ref 物件到 Function Component 內部
語法如下:
const InputField = React.forwardRef((props, ref) => {
return (
<input type="text" ref={ref} />
);
});
const DivItem = React.forwardRef((props, ref) => {
return (
<div ref={ref}>{props.children}</div>
);
});
使用上面用 forwardRef 加工完成的 Enhanced Component,取得參考後,再對其做 DOM 操作
程式碼簡單示意如下
function App() {
const inputRef = React.useRef();
const divRef = React.useRef();
...
// inputRef.current.focus
// divRef.current.style.backgroundColor = '#FF0000'
return (
<div>
<InputField ref={inputRef} />
<DivItem ref={divRef} />
</div>
)
}
完整程式碼執行結果:https://codepen.io/lala-lee-jobs/pen/GRdNvgz
理解完 forwardRef
,就可以再繼續研究 useImperativeHandle
Step 1. 先製作好要傳入 forwardRef 的 Function Component
const CustomComponentFn = (props, ref) => {...}
Step 2. 定義好實際要對應原生 HTML DOM 元素的參考
const CustomComponentFn = (props, ref) => {
const domRef = React.useRef();
}
Step 3. 要回傳的 jsx 綁定好 Step 2 中設定的參考
const CustomComponentFn = (props, ref) => {
const domRef = React.useRef();
return <div ref={domRef} />
}
Step 4. 使用 useImperativeHandle 定義未來使用此元件的 ref 時可以操作的方法
const CustomComponentFn = (props, ref) => {
const domRef = React.useRef();
React.useImperativeHandle(ref, () => ({
doSomething: () => {
// domRef.current.focus();
// domRef.current.value = ....
// domRef.current.style = ....
}
}));
return <div ref={domRef} />
}
Step 5. 使用 fowardRef 包裝好要使用的 Enhanced Component
const CustomComponentFn = (props, ref) => {...}
const CustomComponent = React.forwardRef(CustomComponentFn);
Step 6. 在父元件中使用 Enhanced Component 的 ref 時就可以直接用剛剛在 useImperativeHandle 定義的操作方法
const CustomComponentFn = (props, ref) => {...}
const CustomComponent = React.forwardRef(CustomComponentFn);
const App = () => {
const ccRef = React.useRef();
// 之後就可以直接 ccRef.current.doSomething();
return <CustomComponent ref={ccRef} />
}
我們可以延續上面的 forwardRef 範例,改良成 useImperativeHandle 的用法
const InputFieldFn = (props, ref) => {
const inputRef = React.useRef();
// 使用 useImperativeHandle 定義父元件使用 ref 時可使用的操作方法
React.useImperativeHandle(ref, () => ({
focusAndHightlight: () => {
inputRef.current.focus();
// 設定游標顏色
inputRef.current.style.caretColor = 'red';
}
}));
return (
<div>
<h2>InputField</h2>
<input ref={inputRef} />
</div>
);
}
const InputField = React.forwardRef(InputFieldFn);
const DivItemFn = (props, ref) => {
const divRef = React.useRef();
// 使用 useImperativeHandle 定義父元件使用 ref 時可使用的操作方法
React.useImperativeHandle(ref, () => ({
changeColor: () => {
const randomColor = Math.floor(Math.random()*16777215).toString(16);
divRef.current.style.backgroundColor = "#" + randomColor;
}
}));
return (
<div>
<h2>DivItem</h2>
<div ref={divRef}>{props.children}</div>
</div>
);
}
const DivItem = React.forwardRef(DivItemFn);
const App = () => {
const inputFieldRef = React.useRef();
const divItemRef = React.useRef();
// inputFieldRef.current.focusAndHightlight();
// divItemRef.current.changeColor();
return (
<div>
<InputField ref={inputFieldRef} />
<DivItem ref={divItemRef}>This is the content</DivItem>
</div>
)
}
完整程式碼執行結果:https://codepen.io/lala-lee-jobs/pen/VwxmXpz
fowardRef 及 useImperativeHandle 光看說明比較不好理解,跟著範例實際操作後,就會比較清楚用法,請耐心跟著做看看吧!
下一章要介紹的是可以簡單使用又實用的功能性 Hook - useId
、useLayoutEffect
https://pjchender.dev/react/react-higher-order-component/
https://zh-hant.reactjs.org/docs/hooks-reference.html#useimperativehandle